/*
 * System.cpp
 *
 *  Created on: Jan 22, 2015
 *      Author: wasp
 */

#include "System.hpp"
#include "Session.hpp"
#include "../SBError.hpp"
#include "../base64.h"
#include "../user/UserManager.hpp"
#include "../network/Manager.hpp"

#include <stdio.h>
#include <iostream>
#include <qdebug.h>
#include <sbreturn.h>
#include <qfile.h>
#include <qdir.h>
#include <huctx.h>
#include <hurandom.h>
#include <huseed.h>
#include <hursa.h>
#include <hugse56.h>
#include <openssl/pem.h>
#include <openssl/err.h>


namespace de
{
    namespace heimweghelden
    {
        namespace security
        {
            const int LEVEL = 1024;//4096;
            System* System::_instance = 0;
            System* System::getInstance() {
                if (_instance == 0) {
                    _instance = new System;
                }
                return _instance;
            }
            void System::drop() {
                if (_instance != 0) {
                    delete _instance;
                    _instance = 0;
                }
            }
            void System::deleteRSA() {
                QFile keyFilePriv(myPrivateKeyFilename);
                QFile keyFilePub(myPublicKeyFilename);
                keyFilePriv.remove();
                keyFilePub.remove();
            }

            Session* System::createSession() {
                Session* s = new Session(sbCtx, rngCtx);
                log(s->status(), "System::createSession");
                if (s->status() == SB_SUCCESS)
                {
                    return s;
                }
                delete s;
                return NULL;
            }

            System::System()
            {
                myPrivateKeyFilename = "data/certs/rsa.key.txt";
                myPublicKeyFilename = "data/certs/rsa.pub.txt";

                myKeypair = 0;
                init();
                // 1. RSA System aufbauen
                // 1.1 Wenn kein Schluesselpaar vorhanden ist, neues erstellen und pub-key an server senden
                // 1.2 Schluesselpaar laden
                // 1.3 Pub-Key Manager laden (pubkeys der benutzer vom server laden)
                // 1.4 Auf Anfrage pubkeys zur verwendung laden

                // 2. AES System aufbauen


                //RSA
                {
                    if (initRSA()) {
                        if ( ! keypairExists()) {
                            createKeypair();
                        } else {
                            loadKeypair();
                        }
                    }
                }

                //printRSA(myKeypair);
                test("hallo welt!");
            }

            System::~System()
            {
                int code;
                code = hu_GlobalCtxDestroy(&sbCtx); log(code, "hu_GlobalCtxDestroy");
                if (myKeypair != 0) {
                    BN_free(myKeypair->n);
                    BN_free(myKeypair->e);
                    BN_free(myKeypair->d);
                    BN_free(myKeypair->p);
                    BN_free(myKeypair->q);
                    BN_free(myKeypair->dmp1);
                    BN_free(myKeypair->dmq1);
                    BN_free(myKeypair->iqmp);
                    delete myKeypair;
                }
            }

            bool System::init() {
                // create certs dir
                QDir dataDir("data");
                if ( ! dataDir.exists("certs")) {
                    dataDir.mkdir("certs");
                    if ( ! dataDir.exists("certs/helden")) {
                        dataDir.mkdir("certs/helden");
                    }
                }

                int code;
                code = hu_GlobalCtxCreateDefault(&sbCtx); log(code, "hu_GlobalCtxCreateDefault");
                if (code != SB_SUCCESS) { return false; }

                code = hu_RegisterSbg56(sbCtx); log(code, "hu_RegisterSbg56");
                if (code != SB_SUCCESS) { return false; }

                code = hu_RegisterSystemSeed(sbCtx); log(code, "hu_RegisterSystemSeed");
                if (code != SB_SUCCESS) { return false; }

                code = hu_RegisterSbg56AES(sbCtx); log(code, "hu_RegisterSbg56AES");
                if ( !(code == SB_SUCCESS)) {
                    return false;
                }

                code = hu_InitSbg56(sbCtx); log(code, "hu_InitSbg56");
                if ( !(code == SB_SUCCESS || code == SB_FAIL_LIBRARY_ALREADY_INIT)) {
                    return false;
                }

                return true;
            }

            bool System::initRSA() {
                int code;
                code = hu_RngDrbgCreate(HU_DRBG_CIPHER, 256, false, 0, NULL, NULL, &rngCtx, sbCtx); log(code, "hu_RngDrbgCreate");
                if (SB_SUCCESS != code) {
                    return false;
                }

                code = hu_RSAParamsCreate(LEVEL, rngCtx, NULL, &rsaParams, sbCtx); log(code, "hu_RSAParamsCreate");
                if (SB_SUCCESS != code) {
                    return false;
                }
                return true;
            }

            bool System::keypairExists() {
                QFile keyFilePriv(myPrivateKeyFilename);
                QFile keyFilePub(myPublicKeyFilename);
                return false;//TODO: (keyFilePriv.exists() && keyFilePub.exists());
            }

            bool System::createKeypair() {
                int code = SB_SUCCESS;
                code = hu_RSAKeyGen(rsaParams, 0, NULL, &myPrivateKey, &myPublicKey, sbCtx); log(code, "createKeypair");
                if (code == SB_SUCCESS) {
                    code = createRSAFromKeypair();
                }
                log(code, "createKeypair");
                return code == SB_SUCCESS;
            }

            int System::createRSAFromKeypair() {
                int code;
                size_t eLen = 0, nLen = 0, dLen = 0, pLen = 0, qLen = 0, dModPLen = 0, dModQLen = 0,  qInvLen = 0;

                code = hu_RSAKeyGet
                (
                    rsaParams, myPrivateKey, myPublicKey, &eLen, NULL, &nLen,
                    NULL, &dLen, NULL, &pLen, NULL, &qLen, NULL, &dModPLen,
                    NULL, &dModQLen, NULL, &qInvLen, NULL, sbCtx
                );
                log(code, "hu_RSAKeyGet 1");

                if (code == SB_SUCCESS)
                {
                    unsigned char
                        *e = new unsigned char[eLen],
                        *n = new unsigned char[nLen],
                        *d = new unsigned char[dLen],
                        *p = new unsigned char[pLen],
                        *q = new unsigned char[qLen],
                        *dModPm1 = new unsigned char[dModPLen],
                        *dModQm1 = new unsigned char[dModQLen],
                        *qInvModP = new unsigned char[qInvLen];

                    code = hu_RSAKeyGet
                    (
                        rsaParams, myPrivateKey, myPublicKey, &eLen, e, &nLen,
                        n, &dLen, d, &pLen, p, &qLen, q, &dModPLen, dModPm1,
                        &dModQLen, dModQm1, &qInvLen, qInvModP, sbCtx
                    );
                    log(code, "hu_RSAKeyGet 2");
                    if (code == SB_SUCCESS)
                    {
                        BIGNUM
                            *_e = BN_bin2bn(e, eLen, NULL),
                            *_n = BN_bin2bn(n, nLen, NULL),
                            *_d = BN_bin2bn(d, dLen, NULL),
                            *_p = BN_bin2bn(p, pLen, NULL),
                            *_q = BN_bin2bn(q, qLen, NULL),
                            *_dModPm1 = BN_bin2bn(dModPm1, dModPLen, NULL),
                            *_dModQm1 = BN_bin2bn(dModQm1, dModQLen, NULL),
                            *_qInvModP = BN_bin2bn(qInvModP, qInvLen, NULL);

                        myKeypair = new RSA();
                        myKeypair->n = _n;
                        myKeypair->e = _e;
                        myKeypair->d = _d;
                        myKeypair->p = _p;
                        myKeypair->q = _q;
                        myKeypair->dmp1 = _dModPm1;
                        myKeypair->dmq1 = _dModQm1;
                        myKeypair->iqmp = _qInvModP;
                    }

                    delete e; delete n; delete d; delete p; delete q; delete dModPm1; delete dModQm1; delete qInvModP;
                }
                if (code == SB_SUCCESS) {
                    saveRSA();
                    publishPublicKey();
                }
                return code;
            }

            bool System::saveRSA() {
                return writeRSAPem(true) && writeRSAPem(false);
            }

            bool System::writeRSAPem(bool prikey) {
                QString data = RSAToString(myKeypair, prikey);
                QFile file(prikey ? myPrivateKeyFilename : myPublicKeyFilename);
                bool success = writeFile(data, &file);
                return success;
            }

            bool System::writeFile(QString data, QFile* file) {
                if (file->open(QIODevice::WriteOnly | QIODevice::Text)) {
                    QTextStream out(file);
                    out << data;
                    file->close();
                    return true;
                }
                return false;
            }

            void System::printRSA(RSA* keystore) {
                QString privatePem = RSAToString(keystore, true);
                QString publicPem = RSAToString(keystore, false);
                std::cout << privatePem.toStdString();
                std::cout << std::endl;
                std::cout << publicPem.toStdString();
                std::cout << std::endl;
            }

            QString System::RSAToString(RSA* keystore, bool prikey) {
                BUF_MEM * buff(NULL);
                QString data;
                BIO * bio;
                if (prikey) {
                    bio = BIO_new(BIO_s_mem());
                    PEM_write_bio_RSAPrivateKey(bio, keystore, NULL, NULL, 0, NULL, NULL);
                    BIO_get_mem_ptr(bio, &buff);
                } else {
                    bio = BIO_new(BIO_s_mem());
                    PEM_write_bio_RSAPublicKey(bio, keystore);
                    BIO_get_mem_ptr(bio, &buff);
                }
                data = QString::fromLocal8Bit(buff->data, buff->length);
                BIO_free(bio);
                return data;
            }

            bool System::loadKeypair() {
                qDebug() << "loadKeypair";
                QFile keyFilePriv(myPrivateKeyFilename);
                QFile keyFilePub(myPublicKeyFilename);
                keyFilePriv.open(QIODevice::ReadOnly);
                FILE* fh = fdopen(keyFilePriv.handle(), "rb");
                myKeypair = PEM_read_RSAPrivateKey(fh, NULL, NULL, NULL);
                if (myKeypair == NULL) {
                    qDebug() << "error reading private key";
                    ERR_print_errors_fp(stderr);
                    return false;
                }

                keyFilePub.open(QIODevice::ReadOnly);
                fh = fdopen(keyFilePub.handle(), "rb");
                RSA* tmp = NULL;
                tmp = PEM_read_RSAPublicKey(fh, &myKeypair, NULL, NULL);
                if (tmp == NULL) {
                    qDebug() << "error reading public key";
                    ERR_print_errors_fp(stderr);
                    return false;
                }

                unsigned char *e = new unsigned char[BN_num_bytes(myKeypair->e)];
                int eLen = BN_bn2bin(myKeypair->e, e);

                unsigned char *n = new unsigned char[BN_num_bytes(myKeypair->n)];
                int nLen = BN_bn2bin(myKeypair->n, n);

                unsigned char *d = new unsigned char[BN_num_bytes(myKeypair->d)];
                int dLen = BN_bn2bin(myKeypair->d, d);

                unsigned char *p = new unsigned char[BN_num_bytes(myKeypair->p)];
                int pLen = BN_bn2bin(myKeypair->p, p);

                unsigned char *q = new unsigned char[BN_num_bytes(myKeypair->q)];
                int qLen = BN_bn2bin(myKeypair->q, q);

                unsigned char *dModPm1 = new unsigned char[BN_num_bytes(myKeypair->dmp1)];
                int dModPLen = BN_bn2bin(myKeypair->dmp1, dModPm1);

                unsigned char *dModQm1 = new unsigned char[BN_num_bytes(myKeypair->dmq1)];
                int dModQLen = BN_bn2bin(myKeypair->dmq1, dModQm1);

                unsigned char *qInvModP = new unsigned char[BN_num_bytes(myKeypair->iqmp)];
                int qInvLen = BN_bn2bin(myKeypair->iqmp, qInvModP);

                int code = hu_RSAKeySet
                (
                    rsaParams, eLen, e, nLen, n, dLen, d, pLen, p, qLen, q, dModPLen, dModPm1,
                    dModQLen, dModQm1, qInvLen, qInvModP, &myPrivateKey, &myPublicKey, sbCtx
                );

                delete e;
                delete n;
                delete d;
                delete p;
                delete q;
                delete dModPm1;
                delete dModQm1;
                delete qInvModP;

                return code == SB_SUCCESS;
            }

            void System::publishPublicKey() {
                QString data = RSAToString(myKeypair, false);
                qDebug() << "publishPublicKey";
                de::heimweghelden::user::UserManager::getInstance()->set(de::heimweghelden::user::UserManager::PUBKEY, data);
                de::heimweghelden::network::Manager::getInstance()->publishUserinfo();
            }


            void System::test(QString value) {
                int code;
                const unsigned char* plaintext = reinterpret_cast<unsigned char*>(value.toLocal8Bit().data());
                size_t ciphertextLen;
                code = hu_RSAPKCS1v21SHA1Encrypt
                (
                        rsaParams,
                        myPublicKey,
                        0,
                        NULL,
                        value.length(),
                        plaintext,
                        &ciphertextLen,
                        NULL,
                        sbCtx
                );
                log(code, "cipher test!");
                if (code == SB_SUCCESS) {
                    unsigned char* cipher = new unsigned char[ciphertextLen];
                    code = hu_RSAPKCS1v21SHA1Encrypt
                    (
                            rsaParams,
                            myPublicKey,
                            0,
                            NULL,
                            value.length(),
                            plaintext,
                            &ciphertextLen,
                            cipher,
                            sbCtx
                    );
                    log(code, "cipher test2!");
                    if (code == SB_SUCCESS) {
                        std::string cis = base64_encode(cipher, ciphertextLen);
                        QString ci = QString::fromStdString(cis);
                        qDebug() << ci;

                        //QString t = QString::fromStdString(base64_decode(cis));
                        //unsigned char* cipher2 = reinterpret_cast<unsigned char*>(t.toLocal8Bit().data());

                        std::vector<unsigned char> ret2 = base64_decode(cis);
                        unsigned char* cipher2 = &ret2[0];

                        size_t plaintextLen;
                        code = hu_RSAPKCS1v21SHA1Decrypt
                        (
                                rsaParams, myPrivateKey, 0, NULL,
                                ret2.size(),//ciphertextLen,
                                cipher2,//cipher,
                                &plaintextLen,
                                NULL,
                                sbCtx
                        );
                        log(code, "cipher test3!");
                        if (code == SB_SUCCESS) {
                            unsigned char* plaintext = new unsigned char[plaintextLen];
                            code = hu_RSAPKCS1v21SHA1Decrypt
                            (
                                    rsaParams, myPrivateKey, 0, NULL,
                                    ciphertextLen,
                                    cipher,
                                    &plaintextLen,
                                    plaintext,
                                    sbCtx
                            );

                            log(code, "cipher test4!");
                            char* t = reinterpret_cast<char*>(plaintext);

                            qDebug() << QString::fromLocal8Bit(t, plaintextLen);

                        }
                    }
                }
            }

















            void System::log(int code, QString info = "") {
                if (code != SB_SUCCESS)
                {
                    qDebug() << SBError::getErrorText(code) << info;
                }
            }
        } /* namespace security */
    } /* namespace heimweghelden */
} /* namespace de */
